#pragma once

#include "ckr_spec_Context.h"
#include <cmath>

namespace  ckr
{

//  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

template <typename T>
AssertableValue<T>::AssertableValue(	
							const T&			valueIn,
							const char *	valueNameIn, 
							Context&			callerIn,
							int				lineNumberIn,
							const char*		srcFileNameIn )
	: should_be( *this )
	, value( valueIn )
	, valueName( valueNameIn )
	, caller( callerIn )
	, lineNumber( lineNumberIn )
	, srcFileName( srcFileNameIn )
{
}

//  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


template <typename T>
void AssertableValue<T>::should_be_true()
{
	if ( not ( value ) )
	{
		caller.report_failure_bool( value, valueName, 
			"should be true", lineNumber, srcFileName );
	}
}

//  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


template <typename T>
void AssertableValue<T>::should_be_false()
{
	if ( value  )
	{
		caller.report_failure_bool( value, valueName, 
			"should be false", lineNumber, srcFileName );
	}
}

//  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


template <typename T>
void AssertableValue<T>::should_equal_impl( const T& other, 
			const char* otherName )
{
	if ( not ( value == other ) )
	{
		caller.report_failure_operation( value, valueName, other, 
				otherName, "should equal", lineNumber, srcFileName );
	}
}

template <typename T>
void AssertableValue<T>::operator==(const T& other)
{
	should_equal_impl(other, "");
}

//  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


template <typename T>
void AssertableValue<T>::should_not_equal_impl( const T& other, 
			const char* otherName )
{
	if ( not ( value != other ) )
	{
		caller.report_failure_operation( value, valueName, other, 
				otherName, "should not equal", lineNumber, srcFileName );
	}
}

template <typename T>
void AssertableValue<T>::operator!=(const T& other)
{
	should_not_equal_impl(other, "");
}

//  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


template <typename T>
void AssertableValue<T>::should_be_less_than_impl( const T& other, 
			const char* otherName )
{
	if ( not ( value < other ) )
	{
		caller.report_failure_operation( value, valueName, other, 
				otherName, "should be less than", lineNumber, srcFileName );
	}
}

template <typename T>
void AssertableValue<T>::operator<(const T& other)
{
	should_be_less_than_impl(other, "");
}

//  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


template <typename T>
void AssertableValue<T>::should_be_greater_than_impl( const T& other, 
			const char* otherName )
{
	if ( not ( value > other ) )
	{
		caller.report_failure_operation( value, valueName, other, 
				otherName, "should be greater than", lineNumber, srcFileName );
	}
}

template <typename T>
void AssertableValue<T>::operator>(const T& other)
{
	should_be_greater_than_impl(other, "");
}

//  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


template <typename T>
void AssertableValue<T>::should_be_less_than_or_equal_to_impl( const T& other, 
		const char* otherName )
{
	if ( not ( value <= other ) )
	{
		caller.report_failure_operation( value, valueName, other, 
				otherName, "should be less than or equal to", lineNumber, srcFileName );
	}
}

template <typename T>
void AssertableValue<T>::operator<=(const T& other)
{
	should_be_less_than_or_equal_to_impl(other, "");
}

//  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

template <typename T>
void AssertableValue<T>::should_be_greater_than_or_equal_to_impl( 
		const T& other, const char* otherName )
{
	if ( not ( value >= other ) )
	{
		caller.report_failure_operation( value, valueName, other, 
				otherName, "should be greater than or equal to", lineNumber, srcFileName );
	}
}

template <typename T>
void AssertableValue<T>::operator>=(const T& other)
{
	should_be_greater_than_or_equal_to_impl(other, "");
}

//  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

template <typename T>
void AssertableValue<T>::should_be_between_impl( 
		const T&			lowValueIn,
		const char *	lowValueNameIn, 
		const T&			highValueIn,
		const char *	highValueNameIn )
{
	if ( not ( lowValueIn < value and value < highValueIn ) )
	{
		caller.report_failure_between( value, valueName,
				lowValueIn, lowValueNameIn, 
				highValueIn, highValueNameIn,
				lineNumber, srcFileName );
	}
}

//  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

template <typename T>
void AssertableValue<T>::should_be_close_to_impl( 
		const T&			otherValueIn,
		const char *	otherValueNameIn, 
		const T&			deltaValueIn,
		const char *	deltaValueNameIn )
{
	if ( not ( ( otherValueIn - std::fabs(deltaValueIn) ) <= value 
		and value <= ( otherValueIn + std::fabs(deltaValueIn) ) ) )
	{
		caller.report_failure_close_to( 
			value, valueName,
			otherValueIn, otherValueNameIn, 
			deltaValueIn, deltaValueNameIn,
			lineNumber, srcFileName );
	}
}

//  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

template <typename T>
void AssertableValue<T>::should_not_be_between_impl( 
		const T&			lowValueIn,
		const char *	lowValueNameIn, 
		const T&			highValueIn,
		const char *	highValueNameIn )
{
	if ( lowValueIn < value and value < highValueIn )
	{
		caller.report_failure_not_between( 
			value, valueName,
			lowValueIn, lowValueNameIn, 
			highValueIn, highValueNameIn,
			lineNumber, srcFileName );
	}
}

//  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

template <typename T>
void AssertableValue<T>::should_not_be_close_to_impl( 
		const T&			otherValueIn,
		const char *	otherValueNameIn, 
		const T&			deltaValueIn,
		const char *	deltaValueNameIn )
{
	if ( ( otherValueIn - std::fabs(deltaValueIn) ) <= value 
		and value <= ( otherValueIn + std::fabs(deltaValueIn) ) )
	{
		caller.report_failure_not_close_to( 
			value, valueName,
			otherValueIn, otherValueNameIn, 
			deltaValueIn, deltaValueNameIn,
			lineNumber, srcFileName );
	}
}

//  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

template <typename T>
void AssertableValue<T>::should_be_within_impl( 
		const T&			lowValueIn,
		const char *	lowValueNameIn, 
		const T&			highValueIn,
		const char *	highValueNameIn )
{
	if ( not ( lowValueIn <= value and value <= highValueIn ) )
	{
		caller.report_failure_within( value, valueName,
				lowValueIn, lowValueNameIn, 
				highValueIn, highValueNameIn,
				lineNumber, srcFileName );
	}
}

//  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

template <typename T>
void AssertableValue<T>::should_not_be_within_impl( 
		const T&			lowValueIn,
		const char *	lowValueNameIn, 
		const T&			highValueIn,
		const char *	highValueNameIn )
{
	if ( lowValueIn <= value and value <= highValueIn )
	{
		caller.report_failure_not_within( 
			value, valueName,
			lowValueIn, lowValueNameIn, 
			highValueIn, highValueNameIn,
			lineNumber, srcFileName );
	}
}

//  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

template <typename T>
void AssertableValue<T>::should_be_null_impl()
{
	void * vptr = value;
	
	if ( not ( vptr == NULL ) )
	{
		caller.report_failure_not_null( 
			vptr, valueName, lineNumber, srcFileName );
	}
}

//  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

template <typename T>
void AssertableValue<T>::should_not_be_null_impl()
{
	void * vptr = value;
	
	if ( not ( vptr != NULL ) )
	{
		caller.report_failure_null( 
			vptr, valueName, lineNumber, srcFileName );
	}
}

//  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

}; // end namespace

